/*
  LICENSE
  -------
Copyright 2005 Nullsoft, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

  * Redistributions of source code must retain the above copyright notice,
    this list of conditions and the following disclaimer. 

  * Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution. 

  * Neither the name of Nullsoft nor the names of its contributors may be used to 
    endorse or promote products derived from this software without specific prior written permission. 
 
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/


#include "pluginshell.h"
#include "resource.h"
#include "utility.h"
#include "defines.h"
#include "shell_defines.h"
#include "vis.h"
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <commctrl.h>

#define MAX_PROPERTY_PAGES 8
#define MAX_DISPLAY_ADAPTERS 16
#define MAX_MAX_FPS 120
#define MAX_DISPLAY_MODES 1024

extern winampVisModule mod1;

IDirect3D8* g_lpDX;
HMODULE     g_hmod_d3d8;
D3DADAPTER_IDENTIFIER8 g_disp_adapter_w[MAX_DISPLAY_ADAPTERS];   // NOTE: indices into this list might not equal the ordinal adapter indices!
D3DADAPTER_IDENTIFIER8 g_disp_adapter_fs[MAX_DISPLAY_ADAPTERS];  // NOTE: indices into this list might not equal the ordinal adapter indices!
D3DADAPTER_IDENTIFIER8 g_disp_adapter_dm[MAX_DISPLAY_ADAPTERS];  // NOTE: indices into this list might not equal the ordinal adapter indices!
D3DDISPLAYMODE         g_disp_mode[MAX_DISPLAY_MODES];
HWND        g_config_hwnd;
HWND        g_subwnd;
int         g_num_disp_modes;
int         g_nTab;
int         g_ignore_clicks;
int         g_zero_display_modes_warning_given;
int         g_proppage_id[MAX_PROPERTY_PAGES];

bool CPluginShell::InitConfig(HWND hDialogWnd)
{
    // ******* do initialization of global variables HERE *******
    // ******* do initialization of global variables HERE *******
    // ******* do initialization of global variables HERE *******

    g_lpDX = NULL;
    g_hmod_d3d8 = NULL;
    g_num_disp_modes = 0;
    g_config_hwnd = hDialogWnd;
    g_ignore_clicks = 1;
    g_subwnd = NULL;
    g_nTab   = 0;
    g_zero_display_modes_warning_given = 0;

    g_proppage_id[0] = IDD_PROPPAGE_1;
    g_proppage_id[1] = IDD_PROPPAGE_2;
    g_proppage_id[2] = IDD_PROPPAGE_3;
    g_proppage_id[3] = IDD_PROPPAGE_4;
    g_proppage_id[4] = IDD_PROPPAGE_5;
    g_proppage_id[5] = IDD_PROPPAGE_6;
    g_proppage_id[6] = IDD_PROPPAGE_7;
    g_proppage_id[7] = IDD_PROPPAGE_8;

    // ******* do initialization of global variables HERE *******
    // ******* do initialization of global variables HERE *******
    // ******* do initialization of global variables HERE *******

    return true;
}

void CPluginShell::EndConfig()
{
    SafeRelease(g_lpDX);

    if (g_subwnd)
    {
		DestroyWindow(g_subwnd); 
		g_subwnd = NULL;
    }

    if (g_hmod_d3d8)
    {
        FreeLibrary(g_hmod_d3d8);
        g_hmod_d3d8 = NULL;
    }
}

bool AddButton(int pos, HWND tabctrl, char *szButtonText) 
{ 
    if (strlen(szButtonText) > 0) 
    { 
        TCITEM tie;
        tie.mask = TCIF_TEXT | TCIF_IMAGE; 
        tie.iImage = -1; 
        tie.pszText = szButtonText; 

        if (TabCtrl_InsertItem(tabctrl, pos, &tie) == -1) 
            return false;
    } 

    return true;
}

void CPluginShell::UpdateAdapters(int screenmode)
{
    int i;

    if (!g_lpDX) return;
    
    int nDispAdapters = 0;
    
    HWND ctrl;
    GUID* pGUID = NULL;
    switch(screenmode)
    {
    case FULLSCREEN: 
        ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_FS);
        pGUID = &m_adapter_guid_fullscreen;
        break;
    case WINDOWED: 
        ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_W);
        pGUID = &m_adapter_guid_windowed;
        break;
    /*case FAKE_FULLSCREEN: 
        ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_FFS);
        pGUID = &m_adapter_guid_fake_fullscreen;
        break;*/
    case DESKTOP: 
        ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_DMS);
        pGUID = &m_adapter_guid_desktop;
        break;
    }

    // clear the combo box
    SendMessage( ctrl, CB_RESETCONTENT, 0, 0);

    // repopulate the combo box with a list of adapters
    {
        char szDesc[1024];
        
        D3DADAPTER_IDENTIFIER8* global_adapter_list;
        switch(screenmode)
        {
        case FULLSCREEN: 
            global_adapter_list = g_disp_adapter_fs; 
            break;
        case WINDOWED: 
            global_adapter_list = g_disp_adapter_w; 
            break;
        /*case FAKE_FULLSCREEN: 
            global_adapter_list = g_disp_adapter_w;     // [sic]
            break;*/
        case DESKTOP: 
            global_adapter_list = g_disp_adapter_dm;
            break;
        }

        int nAdapters = g_lpDX->GetAdapterCount();

        // re-populate it:
        for (i=0; i<nAdapters && nDispAdapters<MAX_DISPLAY_ADAPTERS; i++)
        {
            if (g_lpDX->GetAdapterIdentifier(i, D3DENUM_NO_WHQL_LEVEL, &global_adapter_list[nDispAdapters]) == D3D_OK)
            {
                // Now get the caps, and filter out any graphics cards that can't
                // do, say, gouraud shading:

                int adapter_ok = 1;

                /*
                D3DCAPS8 caps;
                if (g_lpDX->GetDeviceCaps(i, D3DDEVTYPE_HAL, &caps)==D3D_OK)
                {
                    // check the caps here, make sure the device is up to par.  example:
                    if (caps.ShadeCaps & D3DPSHADECAPS_COLORGOURAUDRGB)
                      adapter_ok = 0;
                }
                */

                if (adapter_ok)
                {
                    sprintf(szDesc, "%d. %s   [%s]", nDispAdapters+1, global_adapter_list[nDispAdapters].Description, global_adapter_list[nDispAdapters].Driver);
                    SendMessage( ctrl, CB_ADDSTRING, nDispAdapters, (LPARAM)szDesc);
                    nDispAdapters++;
                }
            }
        }    

        // set selection(s):
        // find i where global_adapter_list[i].DeviceIdentifier is the same as last time,
        // and select it.
        int found = 0;
        for (i=0; i<nDispAdapters; i++)
        {
            if (!found && memcmp(&global_adapter_list[i].DeviceIdentifier, pGUID, sizeof(GUID))==0)
            {
                SendMessage( ctrl, CB_SETCURSEL, i, 0);
                found = 1;
            }
        }
        if (!found)
            SendMessage( ctrl, CB_SETCURSEL, 0, 0);
    }

    if (screenmode == FULLSCREEN)
        UpdateFSAdapterDispModes();
    else
        UpdateDispModeMultiSampling(screenmode);
}

void CPluginShell::UpdateFSAdapterDispModes()   // (fullscreen only)
{
    char szfmt[256];
    char str[256];
    int i;
    HWND hwnd_listbox = GetDlgItem( g_subwnd, IDC_DISP_MODE );

    int nVideoModesTotal = 0;
    if (!g_lpDX) return;
    int nAdapterOrdinal = GetCurrentlySelectedAdapter(FULLSCREEN);
    nVideoModesTotal = g_lpDX->GetAdapterModeCount(nAdapterOrdinal);

    if (nVideoModesTotal <= 0 && !g_zero_display_modes_warning_given)
    {
        g_zero_display_modes_warning_given = 1;
        MessageBox(g_config_hwnd, 
            "One or more fullscreen display adapters are behaving strangely and will not\r"
            "report a list of display modes.  It's quite possible that your graphics subsystem\r"
            "is temporarily unstable.\r"
            "\r"
            "SUGGESTED ACTION: If you have modified your multimon setup or Windows,\r"
            "display settings, or have updated any display drivers or installed\r"
            "any new programs, you should probably REBOOT YOUR COMPUTER before\r"
            "trying to configure (or run) the plugin again."
        ,"Warning",MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
    }

    // clear the combo box
    SendMessage( hwnd_listbox, CB_RESETCONTENT, 0, 0);

    int nAdded = 0;

    // re-populate it:
    for (i=0; i<nVideoModesTotal; i++)
    {
        if (nAdded >= MAX_DISPLAY_MODES)
            break;

        // get info for display mode into g_disp_mode[nAdded]
        if (g_lpDX->EnumAdapterModes(nAdapterOrdinal, i, &g_disp_mode[nAdded]) != D3D_OK)
            continue;

        // add to combo box
        int bpp = 0;
        switch(g_disp_mode[nAdded].Format)
        {
        default:
        case D3DFMT_UNKNOWN      : sprintf(szfmt, "UNKNOWN" );   bpp=0;  break;
        case D3DFMT_R8G8B8       : sprintf(szfmt, "RGB-888"  );  bpp=32; break;
        case D3DFMT_A8R8G8B8     : sprintf(szfmt, "ARGB-8888" ); bpp=32; break;
        case D3DFMT_X8R8G8B8     : sprintf(szfmt, "XRGB-8888" ); bpp=32; break;
        case D3DFMT_R5G6B5       : sprintf(szfmt, "RGB-565" );   bpp=16; break;
        case D3DFMT_X1R5G5B5     : sprintf(szfmt, "XRGB-1555");  bpp=16; break;
        case D3DFMT_A1R5G5B5     : sprintf(szfmt, "ARGB-1555");  bpp=16; break;
        case D3DFMT_A4R4G4B4     : sprintf(szfmt, "ARGB-4444");  bpp=16; break;
        case D3DFMT_X4R4G4B4     : sprintf(szfmt, "XRGB-4444");  bpp=16; break;
        }
        sprintf(str, " %s,  %4d x %4d,  %3d Hz ", szfmt, g_disp_mode[nAdded].Width, g_disp_mode[nAdded].Height, g_disp_mode[nAdded].RefreshRate); 

        /*
        #ifdef DONT_ALLOW_8BIT_FULLSCREEN
            if (bpp==8) continue;
        #endif
        #ifdef DONT_ALLOW_16_24_32_BIT_FULLSCREEN
            if (bpp==16 || bpp==24 || bpp==32) continue;
        #endif
        */

        int nPos = SendMessage( hwnd_listbox, CB_ADDSTRING, 0, (LPARAM)str);

        // keep a record of the original index, because the combo box SORTS the data:
        SendMessage( hwnd_listbox, CB_SETITEMDATA, nPos, i);

        nAdded++;
    }

    g_num_disp_modes = nAdded;

    // now set selection, based on best match to prev. video mode.  
    int found = 0;
    
    // Fallback order:
    // 0. exact match (4 params)   w,h,r,f
    // 1. ignore refresh rate, but still heed color format
    // 2. heed only w,h, and if desired_mode.Format is UNKNOWN, 
    //        try for a 16-bpp color format and 60 Hz
    // 3. heed only w,h, and if desired_mode.Format is UNKNOWN, 
    //        try for a 16-bpp color format at any Hz
    // 4. heed only w,h.

    D3DDISPLAYMODE desired_mode = m_disp_mode_fs;

    if (desired_mode.Format==D3DFMT_UNKNOWN)
    {
        // first-time config: try to find a video mode that matches our ideal.
		// do many passes until we find one, each time relaxing more constraints.
		// outline of the passes:

		// PASS  MATCH:
        // 0. w,h,r,16bpp
        // 1. w,h,-,16bpp
        // 2. w,h,r,-
        // 3. w,h,-,-
        // 4. -,-,-,-
        for (int rep=0; rep<5; rep++)
        {
            for (i=0; i<g_num_disp_modes && !found; i++)
            {
                // we have to remap here, because the combo box SORTED the data:
                int id = SendMessage( hwnd_listbox, CB_GETITEMDATA, i, 0);

                bool bDesiredBitDepth = (
                                (g_disp_mode[id].Format == D3DFMT_R8G8B8  ) ||
                                (g_disp_mode[id].Format == D3DFMT_A8R8G8B8) ||     
                                (g_disp_mode[id].Format == D3DFMT_X8R8G8B8) 
                                        ); 

                bool bMatch = true;

                if (rep<=3 && desired_mode.Width!=g_disp_mode[id].Width) 
                    bMatch = false;
                if (rep<=3 && desired_mode.Height!=g_disp_mode[id].Height) 
                    bMatch = false;
                if ((rep==0 || rep==2) && desired_mode.RefreshRate!=g_disp_mode[id].RefreshRate) 
                    bMatch = false;
                if (rep<=1 && !bDesiredBitDepth)
                    bMatch = false;

                if (bMatch)
                {
                    SendMessage( hwnd_listbox, CB_SETCURSEL, i, 0);
                    found = 1;
                }
            }
        }

        // if no match could be found, select #0.
        if (!found)
            SendMessage( hwnd_listbox, CB_SETCURSEL, 0, 0);
    }
    else
    {
        // find best match to prev. selection.
		// do many passes until we find one, each time relaxing more constraints.
		// outline of the passes:

        int bpp_desired = 0;
        switch(desired_mode.Format)
        {
        case D3DFMT_R8G8B8  : bpp_desired = 32; break;
        case D3DFMT_A8R8G8B8: bpp_desired = 32; break;
        case D3DFMT_X8R8G8B8: bpp_desired = 32; break;
        case D3DFMT_R5G6B5  : bpp_desired = 16; break;
        case D3DFMT_X1R5G5B5: bpp_desired = 16; break;
        case D3DFMT_A1R5G5B5: bpp_desired = 16; break;
        case D3DFMT_A4R4G4B4: bpp_desired = 16; break;
        case D3DFMT_R3G3B2  : bpp_desired =  8; break;
        case D3DFMT_A8R3G3B2: bpp_desired = 16; break;
        case D3DFMT_X4R4G4B4: bpp_desired = 16; break;
        }

		// rep   MATCH:
		//  0. w,h,r,f
        //  1. w,h,-,f
        //  2. w,h,r,-         pass: 
        //  3. w,h,-,-           -on pass 0, for 'f', match exact format
        //  4. 8,6,r,f           -on pass 1, for 'f', just match # of bits per pixel
        //  5. 8,6,-,f              (more relaxed match)
        //  6. 8,6,r,-
        //  7. 8,6,-,-
        //  8. -,-,r,f
        //  9. -,-,-,f
        // 10. -,-,r,-
        // 11. -,-,-,-
        for (int rep=0; rep<12 && !found; rep++)
        {
            for (int pass=0; pass<2 && !found; pass++)
            {
                for (i=0; i<g_num_disp_modes && !found; i++)
                {
                    // we have to remap here, because the combo box SORTED the data:
                    int id = SendMessage( hwnd_listbox, CB_GETITEMDATA, i, 0);

                    int bpp_this_mode = 0;
                    switch(g_disp_mode[id].Format)
                    {
                    case D3DFMT_R8G8B8  : bpp_this_mode = 32; break;
                    case D3DFMT_A8R8G8B8: bpp_this_mode = 32; break;
                    case D3DFMT_X8R8G8B8: bpp_this_mode = 32; break;
                    case D3DFMT_R5G6B5  : bpp_this_mode = 16; break;
                    case D3DFMT_X1R5G5B5: bpp_this_mode = 16; break;
                    case D3DFMT_A1R5G5B5: bpp_this_mode = 16; break;
                    case D3DFMT_A4R4G4B4: bpp_this_mode = 16; break;
                    case D3DFMT_R3G3B2  : bpp_this_mode =  8; break;
                    case D3DFMT_A8R3G3B2: bpp_this_mode = 16; break;
                    case D3DFMT_X4R4G4B4: bpp_this_mode = 16; break;
                    }

                    bool bMatch = true;

                    if (rep < 4)
                    {
                        if (desired_mode.Width != g_disp_mode[id].Width) 
                            bMatch = false;
                        if (desired_mode.Height != g_disp_mode[id].Height) 
                            bMatch = false;
                    }
                    else if (rep < 8)
                    {
                        if (DEFAULT_FULLSCREEN_WIDTH != g_disp_mode[id].Width) 
                            bMatch = false;
                        if (DEFAULT_FULLSCREEN_HEIGHT != g_disp_mode[id].Height) 
                            bMatch = false;
                    }
        
                    if (((rep/2)%2)==0)
                    {
                        if (pass==0 && desired_mode.Format != g_disp_mode[id].Format)
                            bMatch = false;
                        else if (pass==1 && bpp_desired != bpp_this_mode)
                            bMatch = false;
                    }

                    if (((rep%2)==0) && desired_mode.RefreshRate!=g_disp_mode[id].RefreshRate) 
                    {
                        bMatch = false;
                    }

                    if (bMatch)
                    {
                        SendMessage( hwnd_listbox, CB_SETCURSEL, i, 0);
                        found = 1;
                    }
                }
            }
        }

        // if no match could be found, select #0.
        if (!found)
            SendMessage( hwnd_listbox, CB_SETCURSEL, 0, 0);
    }

    UpdateDispModeMultiSampling(0);
}

void CPluginShell::UpdateDispModeMultiSampling(int screenmode)
{
    int nSampleTypes = 0;

    HWND hwnd_listbox;
    switch(screenmode)
    {
    case FULLSCREEN:      hwnd_listbox = GetDlgItem(g_subwnd, IDC_FSMS);  break;
    case WINDOWED:        hwnd_listbox = GetDlgItem(g_subwnd, IDC_WMS);   break;
    case DESKTOP:         hwnd_listbox = GetDlgItem(g_subwnd, IDC_DMSMS);  break;
    }

    char str[256];
    int i;

    if (!g_lpDX)
        return;

    if ((screenmode == WINDOWED && !m_allow_page_tearing_w) ||
        (screenmode == FULLSCREEN && m_fake_fullscreen_mode && !m_allow_page_tearing_fs) ||
        (screenmode == DESKTOP && !m_allow_page_tearing_dm))
    {
        // page tearing not allowed -> disable multisampling!
        SendMessage( hwnd_listbox, CB_RESETCONTENT, 0, 0);
        SendMessage( hwnd_listbox, CB_ADDSTRING, 0, (LPARAM)"(disabled/pg. tearing)");
        SendMessage( hwnd_listbox, CB_SETITEMDATA, 0, 0 );
        SendMessage( hwnd_listbox, CB_SETCURSEL, 0, 0);
        EnableWindow( hwnd_listbox, 0 );
    }
    else
    {
        EnableWindow( hwnd_listbox, 1 );

        // figure out which [fullscreen/windowed] adapter is currently selected:
        int nAdapterOrdinal = GetCurrentlySelectedAdapter(screenmode);

        // figure out current format:
        D3DFORMAT format = D3DFMT_UNKNOWN;
        if ((screenmode == WINDOWED) || 
            (screenmode == FULLSCREEN && m_fake_fullscreen_mode) || 
            (screenmode == DESKTOP))
        {
            // ** get it from the current display mode 
            //    of the currently-selected [windowed/fake fullscreen] mode adapter **
            D3DDISPLAYMODE dispmode;
            if (g_lpDX->GetAdapterDisplayMode(nAdapterOrdinal, &dispmode) == D3D_OK)
                format = dispmode.Format;
        }
        else
        {
            // **get it from the currently-selected fullscreen display mode**
            int n = SendMessage( GetDlgItem( g_subwnd, IDC_DISP_MODE ), CB_GETCURSEL, 0, 0);
            if (n != CB_ERR) 
            {
                // since the combobox contents were sorted, we need to look up the original
                // index into g_disp_mode[]:
                n = SendMessage( GetDlgItem( g_subwnd, IDC_DISP_MODE ), CB_GETITEMDATA, n, 0);
                if (n != CB_ERR)
                    format = g_disp_mode[n].Format;
            }
        }

        D3DMULTISAMPLE_TYPE check[16] = {
            D3DMULTISAMPLE_NONE,
            D3DMULTISAMPLE_2_SAMPLES ,
            D3DMULTISAMPLE_3_SAMPLES ,
            D3DMULTISAMPLE_4_SAMPLES ,
            D3DMULTISAMPLE_5_SAMPLES ,
            D3DMULTISAMPLE_6_SAMPLES ,
            D3DMULTISAMPLE_7_SAMPLES ,
            D3DMULTISAMPLE_8_SAMPLES ,
            D3DMULTISAMPLE_9_SAMPLES ,
            D3DMULTISAMPLE_10_SAMPLES,
            D3DMULTISAMPLE_11_SAMPLES,
            D3DMULTISAMPLE_12_SAMPLES,
            D3DMULTISAMPLE_13_SAMPLES,
            D3DMULTISAMPLE_14_SAMPLES,
            D3DMULTISAMPLE_15_SAMPLES,
            D3DMULTISAMPLE_16_SAMPLES,
        };

        // clear the combo box
        SendMessage( hwnd_listbox, CB_RESETCONTENT, 0, 0);

        // re-populate it:
        for (i=0; i<16; i++)
        {
            if (i==0 || SUCCEEDED(g_lpDX->CheckDeviceMultiSampleType(nAdapterOrdinal, D3DDEVTYPE_HAL,
                                                           format, 
                                                           (screenmode==FULLSCREEN) ? 0 : 1,//bWindowed,
                                                           check[i])))
            {
                // add to listbox
                if (i==0)
                    sprintf(str, "(none)");
                else
                    sprintf(str, "%2dX", i+1);

                SendMessage( hwnd_listbox, CB_ADDSTRING, nSampleTypes, (LPARAM)str);

                // set the item data to the D3DMULTISAMPLE_TYPE value:
                SendMessage( hwnd_listbox, CB_SETITEMDATA, nSampleTypes, check[i] );

                nSampleTypes++;
            }
        }

        // set prev. selection
        D3DMULTISAMPLE_TYPE prev_seln;
        switch(screenmode)
        {
        case FULLSCREEN:      prev_seln = m_multisample_fullscreen;      break;
        case WINDOWED:        prev_seln = m_multisample_windowed;        break;
        //case FAKE_FULLSCREEN: prev_seln = m_multisample_fake_fullscreen; break;
        case DESKTOP:         prev_seln = m_multisample_desktop;         break;
        }

        for (i=0; i<nSampleTypes; i++)
        {
            int id = SendMessage( hwnd_listbox, CB_GETITEMDATA, i, 0);
            if (id==prev_seln)
            {
                SendMessage( hwnd_listbox, CB_SETCURSEL, i, 0);
                return;
            }
        }

        SendMessage( hwnd_listbox, CB_SETCURSEL, 0, 0);
    }
}

void CPluginShell::UpdateMaxFps(int screenmode)
{
    // initialize sleep combo boxes
    HWND ctrl;
    switch(screenmode)
    {
    case FULLSCREEN:      ctrl = GetDlgItem(g_subwnd, IDC_FS_MAXFPS ); break;
    case WINDOWED:        ctrl = GetDlgItem(g_subwnd, IDC_W_MAXFPS  ); break;
    //case FAKE_FULLSCREEN: ctrl = GetDlgItem(g_subwnd, IDC_FFS_MAXFPS); break;
    case DESKTOP:         ctrl = GetDlgItem(g_subwnd, IDC_DMS_MAXFPS); break;
    }

    SendMessage( ctrl, CB_RESETCONTENT, 0, 0);
    for (int j=0; j<=MAX_MAX_FPS; j++)
    {
        char buf[256];
        if (j==0)
            sprintf(buf, "(unlimited)");
        else if (j<MAX_MAX_FPS)
            sprintf(buf, "%2d frames/sec", MAX_MAX_FPS+1-j);
        else 
            sprintf(buf, "%2d frame/sec", MAX_MAX_FPS+1-j);
        SendMessage( ctrl, CB_ADDSTRING, j, (LPARAM)buf);
    }

    // set prev. selection
    int max_fps;
    switch(screenmode)
    {
    case FULLSCREEN:      max_fps = m_max_fps_fs; break;
    case WINDOWED:        max_fps = m_max_fps_w;  break;
    //case FAKE_FULLSCREEN: max_fps = m_max_fps_fake_fs; break;
    case DESKTOP:         max_fps = m_max_fps_dm; break;
    }
    if (max_fps == 0)
        SendMessage(ctrl, CB_SETCURSEL, 0,  0);
    else
        SendMessage(ctrl, CB_SETCURSEL, MAX_MAX_FPS-max_fps+1,  0);
}

int CPluginShell::GetCurrentlySelectedAdapter(int screenmode)
{
    // returns the ordinal adapter #.
    HWND ctrl;
    switch(screenmode)
    {
    case FULLSCREEN:      ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_FS ); break;
    case WINDOWED:        ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_W  ); break;
    //case FAKE_FULLSCREEN: ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_FFS); break;
    case DESKTOP:         ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_DMS); break;
    }

    int n = SendMessage(ctrl, CB_GETCURSEL, 0, 0);
    //if (n != CB_ERR) 
    //    n = SendMessage(ctrl, CB_GETITEMDATA, n, 0);
    if (n != CB_ERR) 
        return n;
    else
        return D3DADAPTER_DEFAULT;
}

void CPluginShell::SaveDisplayMode()
{
    //if (m_fake_fullscreen_mode)
    //    return;

    // read fullscreen display mode
    int n = SendMessage( GetDlgItem( g_subwnd, IDC_DISP_MODE ), CB_GETCURSEL, 0, 0);
    if (n != CB_ERR) 
    {
        // since the combobox contents were sorted, we need to look up the original
        // index into g_disp_mode[]:
        n = SendMessage( GetDlgItem( g_subwnd, IDC_DISP_MODE ), CB_GETITEMDATA, n, 0);
        if (n != CB_ERR)
            m_disp_mode_fs = g_disp_mode[n];
    }
}

void CPluginShell::SaveMultiSamp(int screenmode)
{
    HWND ctrl;
    switch(screenmode)
    {
    case FULLSCREEN:      ctrl = GetDlgItem(g_subwnd, IDC_FSMS);  break;
    case WINDOWED:        ctrl = GetDlgItem(g_subwnd, IDC_WMS);   break;
    //case FAKE_FULLSCREEN: ctrl = GetDlgItem(g_subwnd, IDC_FFSMS); break;
    case DESKTOP:         ctrl = GetDlgItem(g_subwnd, IDC_DMSMS); break;
    }

    // if page tearing is disabled, then multisampling must be disabled, 
    // so ignore multisample selection
    if (g_subwnd && g_nTab==0)
    {
        if (screenmode == WINDOWED && !m_allow_page_tearing_w)
            return;
        if (screenmode == DESKTOP && !m_allow_page_tearing_dm)
            return;
        if (screenmode == FULLSCREEN && m_fake_fullscreen_mode && !m_allow_page_tearing_fs)
            return;
    }

    // read windowed & fullscreen multisampling settings:
    int n = SendMessage(ctrl , CB_GETCURSEL, 0, 0);
    if (n != CB_ERR) 
    {
        n = SendMessage( ctrl, CB_GETITEMDATA, n, 0);
        if (n != CB_ERR)
        {
            switch(screenmode)
            {
            case FULLSCREEN:      m_multisample_fullscreen      = (D3DMULTISAMPLE_TYPE)n; break;
            case WINDOWED:        m_multisample_windowed        = (D3DMULTISAMPLE_TYPE)n; break;
            //case FAKE_FULLSCREEN: m_multisample_fake_fullscreen = (D3DMULTISAMPLE_TYPE)n; break;
            case DESKTOP:         m_multisample_desktop         = (D3DMULTISAMPLE_TYPE)n; break;
            }
        }
    }
}

void CPluginShell::SaveMaxFps(int screenmode)
{
    HWND ctrl;
    switch(screenmode)
    {
    case FULLSCREEN:      ctrl = GetDlgItem(g_subwnd, IDC_FS_MAXFPS);  break;
    case WINDOWED:        ctrl = GetDlgItem(g_subwnd, IDC_W_MAXFPS);   break;
    //case FAKE_FULLSCREEN: ctrl = GetDlgItem(g_subwnd, IDC_FFS_MAXFPS); break;
    case DESKTOP:         ctrl = GetDlgItem(g_subwnd, IDC_DMS_MAXFPS); break;
    }

    // read max fps settings
    int n = SendMessage(ctrl, CB_GETCURSEL, 0, 0);
    if (n != CB_ERR) 
    {
        if (n > 0)
            n = MAX_MAX_FPS+1 - n;

        switch(screenmode)
        {
        case FULLSCREEN:      m_max_fps_fs = n; break;
        case WINDOWED:        m_max_fps_w  = n; break;
        //case FAKE_FULLSCREEN: m_max_fps_fake_fs = n; break;
        case DESKTOP:         m_max_fps_dm = n; break;
        }
    }
}

void CPluginShell::SaveAdapter(int screenmode)
{
    HWND ctrl;
    switch(screenmode)
    {
    case FULLSCREEN:      ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_FS);  break;
    case WINDOWED:        ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_W);   break;
    //case FAKE_FULLSCREEN: ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_FFS); break;
    case DESKTOP:         ctrl = GetDlgItem(g_subwnd, IDC_ADAPTER_DMS); break;
    }

    // save windowed/fullscreen adapter
    int n = SendMessage( ctrl, CB_GETCURSEL, 0, 0);
    if (n != CB_ERR) 
    {
        switch(screenmode)
        {
        case FULLSCREEN:      m_adapter_guid_fullscreen      = g_disp_adapter_fs[n].DeviceIdentifier; break;
        case WINDOWED:        m_adapter_guid_windowed        = g_disp_adapter_w[n].DeviceIdentifier;  break;
        //case FAKE_FULLSCREEN: m_adapter_guid_fake_fullscreen = g_disp_adapter_w[n].DeviceIdentifier;  break; // [sic]
        case DESKTOP:         m_adapter_guid_desktop         = g_disp_adapter_dm[n].DeviceIdentifier;  break; 
        }
    }
}

/*
BOOL CALLBACK GenericTabCtrlProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    return 0;
}
*/

// OnTabChanged - processes the TCN_SELCHANGE notification. 
void CPluginShell::OnTabChanged(int nNewTab) 
{ 
    if (g_subwnd)
    {
        DestroyWindow(g_subwnd);
        g_subwnd = NULL;
    }

    g_nTab = nNewTab;

    if (g_nTab >= 0 && g_nTab < MAX_PROPERTY_PAGES)
    {
        CreateDialogParam(
            m_hInstance, 
            MAKEINTRESOURCE(g_proppage_id[g_nTab]), 
            g_config_hwnd, 
            this->TabCtrlProc,//g_proppage_dialogproc[g_nTab],
            (LPARAM)this
        );
        // NOTE: CreateDialogParam will call TabCtrlProc with WM_INITDIALOG,
        // which is where 'g_subwnd' will get set.
    }
}       

BOOL CALLBACK CPluginShell::TabCtrlProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    if (msg==WM_INITDIALOG && lParam > 0 && GetWindowLong(hwnd,GWL_USERDATA)==0)
        SetWindowLong(hwnd, GWL_USERDATA, lParam);

    CPluginShell* p = (CPluginShell*)GetWindowLong(hwnd,GWL_USERDATA);

    if (p && g_nTab >= 0 && g_nTab < MAX_PROPERTY_PAGES)
    {
        if (msg==WM_INITDIALOG)
            g_subwnd = hwnd;

        if (g_nTab==0)
            p->PluginShellConfigTab1Proc(hwnd, msg, wParam, lParam);
        p->MyConfigTabProc(g_nTab+1, hwnd, msg, wParam, lParam);
        
        if (msg==WM_INITDIALOG)
        {
            // once it has been initialized, reposition the subdialog:
            RECT r;
		    GetWindowRect(GetDlgItem(g_config_hwnd,IDC_RECT),&r);
		    ScreenToClient(g_config_hwnd,(LPPOINT)&r);
		    SetWindowPos(g_subwnd,0,r.left,r.top,0,0,SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOZORDER);
	        ShowWindow(g_subwnd,SW_SHOWNA);
        }
    }

    return FALSE;
}

BOOL CPluginShell::PluginShellConfigTab1Proc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    #ifdef _DEBUG
        OutputDebugMessage("   Tab1Proc: ", hwnd, msg, wParam, lParam);
    #endif

    switch (msg)
    {
    case WM_INITDIALOG:
        {
            // pre-checks
            if (m_start_fullscreen && m_start_desktop)
                m_start_desktop = 0;
            if (!mod1.hwndParent || SendMessage(mod1.hwndParent,WM_WA_IPC,0,0) < 0x2900)
            {
                m_skin = 0;
                EnableWindow(GetDlgItem(hwnd,IDC_CB_SKIN), 0);
                char buf[256];
                buf[0] = 0;
                GetWindowText(GetDlgItem(hwnd,IDC_CB_SKIN), buf, 255);
                strcat(buf, " 2.90+");
                SetWindowText(GetDlgItem(hwnd,IDC_CB_SKIN), buf);
            }

            // set checkboxes
            CheckDlgButton(hwnd, IDC_CB_FS,  m_start_fullscreen);
            CheckDlgButton(hwnd, IDC_CB_DMS,  m_start_desktop);
            CheckDlgButton(hwnd, IDC_CB_FAKE, m_fake_fullscreen_mode);
            CheckDlgButton(hwnd, IDC_CB_PRESS_F1_MSG, m_show_press_f1_msg);
            CheckDlgButton(hwnd, IDC_CB_WPT, m_allow_page_tearing_w);
            CheckDlgButton(hwnd, IDC_CB_FSPT, m_allow_page_tearing_fs);
            CheckDlgButton(hwnd, IDC_CB_DMSPT, m_allow_page_tearing_dm);
            CheckDlgButton(hwnd, IDC_CB_MIN, m_minimize_winamp);
            CheckDlgButton(hwnd, IDC_CB_SAVE_CPU, m_save_cpu);
            CheckDlgButton(hwnd, IDC_CB_SKIN, m_skin);
			CheckDlgButton(hwnd, IDC_CB_FIXSLOWTEXT, m_fix_slow_text);
            CheckDlgButton(hwnd, IDC_CB_VJMODE, m_vj_mode);

            // Enumerate available adapters.
            UpdateAdapters(0);    // fullscreen
              //-calls UpdateFSAdapterDispModes()
              //-which then calls UpdateDispModeMultiSampling(0).
            UpdateAdapters(1);    // windowed
              //-skips UpdateFSAdapterDispModes() (not necessary for windowed mode)
              //-then calls UpdateDispModeMultiSampling(1).
            UpdateAdapters(3);    // desktop
              //-skips UpdateFSAdapterDispModes() (not necessary for fake fullscreen mode)
              //-then calls UpdateDispModeMultiSampling(2).
            UpdateMaxFps(0);
            UpdateMaxFps(1);
            UpdateMaxFps(3); // desktop

            // disable a few things if fake fullscreen mode enabled:
            EnableWindow(GetDlgItem(hwnd, IDC_DISP_MODE), !m_fake_fullscreen_mode);
            //EnableWindow(GetDlgItem(hwnd, IDC_FSMS), !m_fake_fullscreen_mode);
        }        
        break;

    case WM_DESTROY:
        {
            // read checkboxes
            m_start_fullscreen     = DlgItemIsChecked(hwnd, IDC_CB_FS   );
            m_start_desktop        = DlgItemIsChecked(hwnd, IDC_CB_DMS  );
            m_fake_fullscreen_mode = DlgItemIsChecked(hwnd, IDC_CB_FAKE );
            m_show_press_f1_msg    = DlgItemIsChecked(hwnd, IDC_CB_PRESS_F1_MSG);
            m_allow_page_tearing_w = DlgItemIsChecked(hwnd, IDC_CB_WPT  );
            m_allow_page_tearing_fs= DlgItemIsChecked(hwnd, IDC_CB_FSPT  );
            m_allow_page_tearing_dm= DlgItemIsChecked(hwnd, IDC_CB_DMSPT);
            m_minimize_winamp      = DlgItemIsChecked(hwnd, IDC_CB_MIN  );
            m_save_cpu             = DlgItemIsChecked(hwnd, IDC_CB_SAVE_CPU );
            m_fix_slow_text        = DlgItemIsChecked(hwnd, IDC_CB_FIXSLOWTEXT);
            m_vj_mode              = DlgItemIsChecked(hwnd, IDC_CB_VJMODE);
            
            if (mod1.hwndParent && SendMessage(mod1.hwndParent,WM_WA_IPC,0,0) >= 0x2900)
                m_skin             = DlgItemIsChecked(hwnd, IDC_CB_SKIN );

            // read all 3 adapters
            SaveAdapter(0);
            SaveAdapter(1);
            SaveAdapter(3);

            // read fullscreen display mode
            SaveDisplayMode();

            // read all 3 multisampling settings:
            SaveMultiSamp(0);
            SaveMultiSamp(1);
            SaveMultiSamp(3);

            // read all 3 max fps settings
            SaveMaxFps(0);
            SaveMaxFps(1);
            SaveMaxFps(3);
        }
        break;

    case WM_COMMAND:
        if (!g_ignore_clicks)
        {
            int id = LOWORD(wParam);
            g_ignore_clicks = 1;
            switch(id)
            {
			case ID_FONTS:
                DialogBoxParam(m_hInstance, MAKEINTRESOURCE(IDD_FONTDIALOG), hwnd, (DLGPROC)FontDialogProc, (LPARAM)this);
                break;

			case ID_DM_MORE:
                DialogBoxParam(m_hInstance, MAKEINTRESOURCE(IDD_DESKTOPMODE), hwnd, (DLGPROC)DesktopOptionsDialogProc, (LPARAM)this);
                break;

			case ID_DUALHEAD:
                DialogBoxParam(m_hInstance, MAKEINTRESOURCE(IDD_DUALHEAD), hwnd, (DLGPROC)DualheadDialogProc, (LPARAM)this);
                break;

            case IDC_ADAPTER_FS:
                SaveDisplayMode();
                SaveMultiSamp(FULLSCREEN);
                SaveAdapter(0);
                UpdateFSAdapterDispModes();
                break;

            case IDC_ADAPTER_W:
                SaveMultiSamp(WINDOWED);
                UpdateDispModeMultiSampling(WINDOWED);
                break;

                /*
            case IDC_ADAPTER_FFS:
                SaveMultiSamp(FAKE_FULLSCREEN);
                UpdateDispModeMultiSampling(FAKE_FULLSCREEN);
                break;
                */

            case IDC_ADAPTER_DMS:
                SaveMultiSamp(DESKTOP);
                UpdateDispModeMultiSampling(DESKTOP);
                break;
                
            case IDC_DISP_MODE:
                SaveMultiSamp(FULLSCREEN);
                UpdateDispModeMultiSampling(FULLSCREEN);
                break;

            case IDC_CB_WPT:
                SaveMultiSamp(WINDOWED);
                m_allow_page_tearing_w = DlgItemIsChecked(hwnd, IDC_CB_WPT);
                UpdateDispModeMultiSampling(WINDOWED);
                break;

            case IDC_CB_FSPT:
                SaveMultiSamp(FULLSCREEN);
                m_allow_page_tearing_fs = DlgItemIsChecked(hwnd, IDC_CB_FSPT);
                UpdateDispModeMultiSampling(FULLSCREEN);
                break;

            case IDC_CB_DMSPT:
                SaveMultiSamp(DESKTOP);
                m_allow_page_tearing_dm = DlgItemIsChecked(hwnd, IDC_CB_DMSPT);
                UpdateDispModeMultiSampling(DESKTOP);
                break;

            case IDC_CB_FS:
                m_start_fullscreen     = DlgItemIsChecked(hwnd, IDC_CB_FS   );
                if (m_start_fullscreen && m_start_desktop)
                {
                    m_start_desktop = 0;
                    CheckDlgButton(hwnd, IDC_CB_DMS,  m_start_desktop);
                }
                break;
            
            case IDC_CB_DMS:
                m_start_desktop        = DlgItemIsChecked(hwnd, IDC_CB_DMS  );
                if (m_start_fullscreen && m_start_desktop)
                {
                    m_start_fullscreen = 0;
                    CheckDlgButton(hwnd, IDC_CB_FS,  m_start_fullscreen);
                }
                break;

            case IDC_CB_FAKE:
                SaveMultiSamp(FULLSCREEN);
                m_fake_fullscreen_mode = DlgItemIsChecked(hwnd, IDC_CB_FAKE );
                EnableWindow(GetDlgItem(hwnd, IDC_DISP_MODE), !m_fake_fullscreen_mode);
                CheckDlgButton(hwnd, IDC_CB_FSPT, m_fake_fullscreen_mode ? m_allow_page_tearing_fs : 0);
                EnableWindow(GetDlgItem(hwnd, IDC_CB_FSPT), m_fake_fullscreen_mode ? 1 : 0);
                UpdateDispModeMultiSampling(FULLSCREEN);
                break;

                /*
            case IDC_CB_FFSPT:
                SaveMultiSamp(FAKE_FULLSCREEN);
                m_allow_page_tearing_fake_fs = DlgItemIsChecked(hwnd, IDC_CB_FFSPT);
                UpdateDispModeMultiSampling(FAKE_FULLSCREEN);
                break;
                */
            }
            g_ignore_clicks = 0;
        }
        break;  // case WM_COMMAND

    case WM_HELP:
        if (lParam)
        {
            HELPINFO *ph = (HELPINFO*)lParam;
            char title[256];
            char buf[2048];
            char ctrl_name[256];
            GetWindowText(GetDlgItem(hwnd, ph->iCtrlId), ctrl_name, sizeof(ctrl_name)-1);
            RemoveSingleAmpersands(ctrl_name);
            buf[0] = 0;
            
            switch(ph->iCtrlId)
            {
            case ID_FONTS:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, "Click this button to alter the fonts that are used to display text\rwhile the plugin is running.");
                break;
            case ID_DUALHEAD:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, 
                    "Click this button for special options concerning DualHead video cards\r"
                    "that are capable of running in double-width or double-height display\r"
                    "modes.  DualHead cards that support this treat the two monitors as\r"
                    "one giant, double-width or double-height display.\r"
                    "\r"
                    "This would usually mean that when going Fullscreen, the plugin would be\r"
                    "stretched over both monitors.  However, when you run the plugin in\r"
                    "Desktop Mode or in *Fake* Fullscreen Mode, you can get around this,\r"
                    "and allow the plugin to run on just one monitor, leaving you free\r"
                    "to work on the other.\r"
                );
                break;
            case IDC_W_MULTISAMPLING_CAPTION:
            case IDC_FS_MULTISAMPLING_CAPTION:
            case IDC_DMS_MULTISAMPLING_CAPTION:
            //case IDC_FFS_MULTISAMPLING_CAPTION:
            case IDC_WMS:
            case IDC_FSMS:
            case IDC_DMSMS:
            //case IDC_FFSMS:
                sprintf(title, "Help on Multisampling");
                sprintf(buf, 
                    "This controls the level of full-scene anti-aliasing (blending)\r"
                    "that the display adapter uses.  Only newer video cards will\r"
                    "tend to support this feature.  Anti-aliasing softens out the\r"
                    "'jaggies' that you sometimes see at the pixel level, for example,\r"
                    "at the silhouette of an object, or on a wireframe object.\r"
                    "\r"
                    "A level of '2X' would mean that 3D rendering/rasterization is done\r"
                    "internally *at double-resolution*, and then downsampled before final\r"
                    "display to the user.  A level of '3X' would mean that the rendering\r"
                    "is done at triple-resolution, and so on.\r"
                    "\r"
                    "Note that, due to limitations of the DirectX 8 API, the use of multisampling\r"
                    "generally requires that page tearing be allowed; the one exception is 'true'\r"
                    "fullscreen mode, where you can have the best of both worlds: you can disable\r"
                    "page tearing and enable multisampling at the same time."
                );
                break;

            case IDC_W_MAXFPS:
            case IDC_FS_MAXFPS:
            case IDC_DMS_MAXFPS:
            //case IDC_FFS_MAXFPS:
            case IDC_W_MAXFPS_CAPTION:
            case IDC_FS_MAXFPS_CAPTION:
            case IDC_DMS_MAXFPS_CAPTION:
            //case IDC_FFS_MAXFPS_CAPTION:
                sprintf(title, "Help on Max Framerate", ctrl_name);
                sprintf(buf, 
                    "This controls the maximum # of times the image will be updated, per second.\r"
                    "Framerate is also commonly called \"FPS\", or \"frames per second.\"\r"
                    "\r"
                    "If the plugin is running too quickly for your taste, you can lower the\r"
                    "maximum framerate to slow it down.  You might also want to do this if\r"
                    "the plugin is 'hogging the CPU' and slowing down other applications.\r"
                    "\r"
                    "Typically, a framerate of 30 looks good; 60 looks great.  The human eye\r"
                    "has a hard time processing more than 60 fps, though.\r"
                    "\r"
                    "Note that if the animation is rendering below the maximum framerate here,\r"
                    "your CPU will likely be in 100%% use, leaving your computer somewhat\r"
                    "unresponsive.  If this is the case, try lowering the max. framerate\r"
                    "until it takes effect, and then your computer should become more responsive.\r"
                    "\r"
                    "Also keep in mind that it's a good idea to try and set the max. framerate\r"
                    "to the current refresh rate of your monitor (e.g. 60 Hz, 72 Hz, etc.)\r"
                    "or an integer factor of that number; for example, at a 72 Hz monitor\r"
                    "refresh rate, good max. framerates would be 2, 3, 4, 6, 8, 9, 12, 18,\r"
                    "24, 36, or 72.\r"
                    "\r"
                    );
                break;

            case IDC_CB_FAKE:
                sprintf(title, "Help on 'fake' fullscreen mode");
                sprintf(buf, 
                    "When this option is enabled, the display mode is never changed by\r"
                    "the plugin; instead, the plugin 'fakes' fullscreen mode by simply\r"
                    "making a window that takes up the entire screen, and trying to keep\r"
                    "the window on top.\r"
                    "\r"
                    "DUALHEAD: Fake fullscreen mode can be especially handy when you have a\r"
                    "dualhead display adapter that drives two monitors, but really just treats\r"
                    "them as one giant display (e.g. **the display mode is something like 2048x768\r"
                    "or 1024x1536**), where regular fullscreen mode will take over both monitors,\r"
                    "creating a very large, stretched image.  HOWEVER, if you use fake\r"
                    "fullscreen mode, it can (if you want) take over just one monitor, leaving\r"
                    "you free to work on the other monitor.  To choose which display to favor\r"
                    "(or to span both), click the 'DualHead' button and follow the instructions\r"
                    "there.\r"
                    "\r"
                    "TASKBAR: Note that in fake fullscreen mode, the taskbar will still be visible\r"
                    "(and usable) if the plugin does not entirely cover all displays & all monitors.\r"
                    "If you don't like it, use 'true' fullscreen mode instead, or move the taskbar\r"
                    "(...there should be a place to move it to, since the plugin doesn't cover all\r"
                    "of your displays).  However, if the plugin does cover all displays and all\r"
                    "monitors, the taskbar should be nicely hidden."
                    );
                break;

            case IDC_ADAPTER_FS:
            case IDC_FS_ADAPTER_CAPTION:
                sprintf(title, "Help on fullscreen display adapter selection");
                sprintf(buf, 
                    "This lets you select which display adapter (video card) you'd like to\r"
                    "run the plugin on, when it goes fullscreen.  If you only have one video\r"
                    "card and monitor in your system, you will only have one choice here; but\r"
                    "if you have multiple video cards, or a multi-head video card (one card\r"
                    "that drives multiple monitors), there should be two or more choices here.\r"
                    "\r"
                    "Note that if you have trouble running in fullscreen mode with multiple\r"
                    "displays, you might want to try the 'fake fullscreen mode' option.\r"
                    "See its help screen for more information."
                    );
                break;

            case IDC_ADAPTER_W:
            case IDC_W_ADAPTER_CAPTION:
                sprintf(title, "Help on windowed-mode display adapter selection");
                sprintf(buf, 
                    "This lets you select which display adapter (video card) you'd like to\r"
                    "run the plugin on, when it runs in a window.  If you only have one video\r"
                    "card and monitor in your system, you will only have one choice here; but\r"
                    "if you have multiple video cards, or a multi-head video card (one card\r"
                    "that drives multiple monitors), there should be two or more choices here.\r"
                    "\r"
                    "The default window position will be somewhere on the monitor for the\r"
                    "display adapter you choose here.  You can drag the window to a different\r"
                    "monitor, but it is likely to be very slow, because the pixels will have\r"
                    "to be copied, via Windows, from one display to the next, each frame.\r"
                    "\r"
                    "So, for best performance in windowed mode, be sure to select the monitor\r"
                    "you want it to run on here, in advance, and avoid dragging the window to\r"
                    "another monitor at runtime."
                    );
                break;

            case IDC_ADAPTER_DMS:
            case IDC_DMS_ADAPTER_CAPTION:
                sprintf(title, "Help on desktop-mode display adapter selection");
                sprintf(buf, 
                    "This lets you select which display adapter (video card & monitor) you'd like to\r"
                    "run the plugin on, when it runs in desktop mode, replacing your windows wallpaper.\r"
                    "If you only have one video card and monitor in your system, you will only have\r"
                    "one choice here; but if you have multiple video cards, or a multi-head video card\r"
                    "(one card that drives multiple monitors), there should be two or more choices here.\r"
                    );
                break;

                /*
            case IDC_ADAPTER_FFS:
            case IDC_FFS_ADAPTER_CAPTION:
                sprintf(title, "Help on fake-fullscreen-mode display adapter selection");
                sprintf(buf, 
                    "This lets you select which display adapter (video card) you'd like to\r"
                    "run the plugin on, when it runs in fake fullscreen mode.  If you only have one video\r"
                    "card and monitor in your system, you will only have one choice here; but\r"
                    "if you have multiple video cards, or a multi-head video card (one card\r"
                    "that drives multiple monitors), there should be two or more choices here.\r"
                    "\r"
                    "For more information about fake fullscreen mode, see the help topic\r"
                    "associated with the 'Use Fake fullscreen mode' checkbox."
                    );
                break;
                */

            case IDC_DISP_MODE:
            case IDC_DISP_MODE_CAPTION:
                sprintf(title, "Help on fullscreen display mode selection");
                sprintf(buf, 
                    "This lets you select which display mode you'd like to use when you\r"
                    "run the plugin fullscreen.\r"
                    "\r"
                    "The first parameter is the pixel color format, which decides\r"
                    "the total number of possible colors in the rendered image.\r"
                    "RGB-555 and RGB-565 are 16-bit color formats, which have poor\r"
                    "color resolution but are often fast.  RGB-888 is a 32-bit color\r"
                    "format; it often has superior image quality, but is often slower\r"
                    "and takes up twice the video memory.\r"
                    "\r"
                    "The next two parameters are the width and height of the display mode,\r"
                    "in pixels, also known as the 'resolution'.  Higher resolutions are\r"
                    "usually slower and require more video memory, but look better.\r"
                    "\r"
                    "The last parameter is the refresh rate: the rate at which the\r"
                    "monitor refreshes the image you see, in Hertz (cycles per second).\r"
                    "Higher refresh rates tend to be easier on the eyes."
                    );
                break;

            case IDC_CB_WPT:
            case IDC_CB_FSPT:
            case IDC_CB_DMSPT:
            //case IDC_CB_FFSPT:
                sprintf(title, "Help on '%s' checkbox", ctrl_name);
                sprintf(buf, 
                    "When a new frame of animation is ready for display, the plugin\r"
                    "has a choice of whether or not to synchronize the new frame to the\r"
                    "monitor's next vertical scan.\r"
                    "\r"
                    "If there is no synchronization and the new frame is shown immediately,\r"
                    "then the update might occure in 'mid-scan', so for that 1/60th of a second,\r"
                    "you'll see the old frame toward the top of the monitor, and the new frame\r"
                    "toward the bottom, with a sharp line ('tear') somewhere in between.  This\r"
                    "is especially visible when solid objects on the screen are changing or\r"
                    "moving rapidly, from frame to frame.\r"
                    "\r"
                    "However, if the plugin waits until the vertical scan is complete to update\r"
                    "the image, then at the start of the next vertical scan, the entire image\r"
                    "will be ready and will (hopefully) be presented without any tearing.\r"
                    "\r"
                    "Although page tearing will often cause visible artifacts, it will allow\r"
                    "the plugin to run at its maximum possible framerate.  Generally, though,\r"
                    "page tearing is considered a bad thing, to be avoided when possible.\r"
                    "\r"
                    "Note that this synchronization (done by DirectX and your video driver)\r"
                    "is usually approximate when running in windowed (or fake fullscreen) modes;\r"
                    "thus, page tearing is often minimized but can't always be completely eliminated.\r"
                    "\r"
                    "Note that multisampling is usually only available when page tearing is\r"
                    "allowed, due to limitations of the DirectX 8 API.  The one exception is\r"
                    "'true' fullscreen mode; there, you can disable page tearing and still use\r"
                    "multisampling.  (But not in fake fullscreen, windowed, or desktop modes.)"
                );
                break;

            case IDC_CB_FS:
                sprintf(title, "Help on '%s' checkbox", ctrl_name);
                sprintf(buf, "Enable this to force the plugin to start in fullscreen\r(or 'fake fullscreen') mode.\r\r(Note that if 'fake' fullscreen mode is enabled,\rthe plugin will start in fake fullscreen mode.)");
                break;

            case IDC_CB_DMS:
                sprintf(title, "Help on '%s' checkbox", ctrl_name);
                sprintf(buf, 
                    "Enable this to force the plugin to start in desktop mode.\r"
                    /*"\r"
                    "If this option is greyed out, it is because Desktop Mode\r"
                    "is not available on your computer.  (Desktop Mode requires\r"
                    "Windows 2000 or Windows XP to work properly.)"*/
                );
                break;

            case IDC_CB_PRESS_F1_MSG:
                sprintf(title, "Help on 'Press F1...' checkbox");
                sprintf(buf,   "Disable this to prevent the 'Press F1 for Help' message\r"
                               "from appearing when the plugin starts.");
                break;

            case IDC_CB_SKIN:
                sprintf(title, "Help on '%s' checkbox", ctrl_name);
                sprintf(buf,   
                    "Check this box to 'skin' the plugin's window frame when it runs\r"
                    "in a window, so that it looks like the rest of the windows that\r"
                    "make up Winamp's interface.\r"
                    "\r"
                    "This feature requires Winamp 2.90 or later; if the box is greyed\r"
                    "out, it means you need a newer version of Winamp."
                );
                break;

            case IDC_CB_SAVE_CPU:
                sprintf(title, "Help on 'Save CPU' checkbox");
                sprintf(buf,   
                    "Check this box to lower the amount of CPU (processor) that the plugin uses\r"
                    "to monitor and limit the framerate.\r"
                    "\r"
                    "When this box is unchecked, the plugin will extremely accurately limit the\r"
                    "framerate, resulting in ultra-smooth animation.  However, there is some cost\r"
                    "in terms of CPU time - usually in the range of 0-20%%.\r"
                    "\r"
                    "When this box is checked, though, the plugin uses a more lenient algorithm to\r"
                    "limit the framerate, which uses virtually no CPU time.  However, the framerate\r"
                    "will not be as accurately limited (it might vary over time), and animation is\r"
                    "not guaranteed to be perfectly smooth.\r"
                    "\r"
                    "Note that you can further decrease CPU usage by:\r"
                    "  1) decreasing the maximum framerate (via the 'Max Framerate' options)\r"
                    "  2) allowing page tearing (via the 'Allow Page Tearing' checkboxes)\r"
                );
                break;

            case IDC_CB_MIN:
                sprintf(title, "Help on 'Minimize Winamp...' checkbox");
                sprintf(buf, 
                    "Enable this if you can sometimes see the Winamp window flickering\r"
                    "through, when you run the plugin fullscreen.  Enabling this option\r"
                    "will force the Winamp window to minimize whenever the plugin goes\r" 
                    "fullscreen on the same monitor that the Winamp window is on.\r"
                    "The Winamp window will be restored as soon as the plugin returns\r"
                    "from fullscreen mode.\r"
                    "\r"
                    "Note that this also applies for 'fake' fullscreen mode (as well as 'real'\r"
                    "fullscreen mode).\r"
                    "\r"
                    "Keep in mind that when running graphically demanding applications\r"
                    "(such as this plugin), it's always good to minimize (or close) as many\r"
                    "other applications as possible, even if they appear to be idle."
                );
                break;

            case IDC_CB_FIXSLOWTEXT:
                sprintf(title, "Help on 'Try to fix slow text' checkbox", ctrl_name);
                sprintf(buf, 
                    "Many video cards - even new ones - are very slow at drawing onscreen text.\r"
                    "Most application render onscreen text overtop of the final image\r"
                    "*every frame*, but since MilkDrop has so many menus, this can severely\r"
                    "drop the framerate.\r"
                    "\r"
                    "A workaround is invoked by checking this box.  Instead of drawing the\r"
                    "text every frame, it is instead drawn to a second image buffer, and then\r"
                    "only updated when the text changes.  That separate image buffer is then\r"
                    "overlaid onto the final image for display, each frame.  By not redrawing\r"
                    "the same text over and over, MilkDrop maintains a normal framerate.\r"
                    "\r"
                    "This option is good for maintaining speed, but it uses a substantial amount\r"
                    "of video memory.  You should probably only use it if you have 32 megabytes\r"
                    "(or more) of graphics memory.  If there is not enough video memory to set\r"
                    "up the offscreen image buffer for cacheing the text, it will not be created,\r"
                    "and MilkDrop will behave as if the box was unchecked."
                );
                break;

            case IDC_CB_VJMODE:
                sprintf(title, "Help on VJ Mode", ctrl_name);
                sprintf(buf, 
                    "VJ mode is used by those who want to show MilkDrop on one display\r"
                    "while working with it on the other display.  When VJ mode is on,\r"
                    "text is drawn into a second window, instead of being overlaid on\r"
                    "top of the graphics.  This leaves the 'VJ' (the person running the\r"
                    "visuals for a concert, for example) able to navigate milkdrop's menus\r"
                    "and edit presets while MilkDrop runs on a second display (likely\r"
                    "running to a projector), without the ugly menus showing up.\r"
                    "\r"
                    "IMPORTANT: whichever windowed mode display adapter you have selected \r"
                    "determines which monitor the separate text window appears on, and runs\r"
                    "best on.  If you try dragging (moving) it to another monitor, performance\r"
                    "will be severely impacted; instead, exit MilkDrop, return to the config\r"
                    "panel, and set the windowed mode display adapter to reflect the monitor\r"
                    "you want the separate text window to appear on.\r"
                );
                break;

            case IDC_DMS_LABEL:
                sprintf(title, "Help on '%s'", ctrl_name);
                sprintf(buf, 
                    "These settings control the behavior of the plugin when it is running\r"
                    "in 'desktop mode'.  In desktop mode, the plugin is visible in your\r"
                    "Windows background, replacing your wallpaper with the animated plugin.\r"
                    /*"\r"
                    "Unfortunately, Desktop Mode only works with Windows NT, 2000, and XP\r"
                    "at present (95, 98, and ME are unsupported); if Desktop Mode is not\r"
                    "supported on your machine, the settings options for it will be greyed\r"
                    "out."*/
                );
                break;

                /*
            case IDC_FFS_LABEL:
                sprintf(title, "Help on '%s'", ctrl_name);
                sprintf(buf, 
                    "These settings control the behavior of the plugin when it is running\r"
                    "in 'fake fullscreen mode'.  This mode makes it look like the plugin\r"
                    "is running fullscreen, but in fact, it is technically running in\r"
                    "windowed mode; the window just takes up the whole screen.\r"
                    "\r"
                    "'Fake' fullscreen can come in handy if you have other applications\r"
                    "that don't cooperate when another app (e.g. this plugin) runs with\r"
                    "DirectX in 'exclusive' (i.e. true fullscreen) mode.\r"
                    "\r"
                    "It can also come in handy when you have a dualhead display adapter\r"
                    "that drives two monitors, but really just treats them as one giant\r"
                    "display (e.g. **the display mode is something like 2048x768 or\r"
                    "1024x1536**), where regular fullscreen mode will take over both monitors,\r"
                    "creating a very large, stretched image.  HOWEVER, if you use 'fake'\r"
                    "fullscreen mode, it will take over just one monitor, leaving you\r"
                    "free to work on the other monitor.\r"
                    "\r"
                    "One drawback to faking fullscreen mode is that performance might be\r"
                    "degraded, since the plugin is not running in 'exclusive' mode."
                );
                MessageBox(hwnd, buf, title, MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
                break;
                */
                
            case IDC_FS_LABEL:
                sprintf(title, "Help on '%s'", ctrl_name);
                sprintf(buf, 
                    "These settings control the behavior of the plugin when it is running\r"
                    "in fullscreen mode.  In fullscreen mode, the plugin changes the display\r"
                    "mode to whatever you indicate here, and then uses the entire display\r"
                    "to render the image.\r"
                    "\r"
                    "Fullscreen mode also gives the plugin certain privileges within the\r"
                    "graphics subsystem (it enters 'exclusive mode'), so performance is\r"
                    "usually quite improved."
                );
                break;
                
            case IDC_W_LABEL:
                sprintf(title, "Help on '%s'", ctrl_name);
                sprintf(buf, 
                    "These settings control the behavior of the plugin when it is running\r"
                    "in a window.  In windowed mode, the plugin renders right into a window,\r"
                    "just like any other application you might have running.\r"
                    "\r"
                    "When the plugin starts, the default window position will be somewhere\r"
                    "on the monitor for the display adapter you choose here.\r"
                    "You *can* drag the window to a different monitor, but it is likely to be,\r"
                    "VERY slow because the pixels will have to be copied, via Windows, from\r"
                    "one display to the next, each frame.\r"
                    "\r"
                    "So, for best performance in windowed mode, be sure to select the monitor\r"
                    "you want it to run on here, in advance, and avoid dragging the window to\r"
                    "another monitor at runtime."
                );
                break;

            case ID_DM_MORE:
                sprintf(title, "Help on '%s'", ctrl_name);
                sprintf(buf, 
                    "Click here to bring up a dialog with\r"
                    "advanced settings for Desktop Mode."
                );
                break;
            }

            if (buf[0])
                MessageBox(hwnd, buf, title, MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);

        }
        break;  // case WM_HELP
    }

    return 0;
}

BOOL CALLBACK CPluginShell::ConfigDialogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    if (msg==WM_INITDIALOG && lParam > 0 && GetWindowLong(hwnd,GWL_USERDATA)==0)
        SetWindowLong(hwnd, GWL_USERDATA, lParam);

    CPluginShell* p = (CPluginShell*)GetWindowLong(hwnd,GWL_USERDATA);

    if (p)
        return p->PluginShellConfigDialogProc(hwnd, msg, wParam, lParam);
    else
        return FALSE;
}

BOOL CPluginShell::PluginShellConfigDialogProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
    #ifdef _DEBUG
        OutputDebugMessage("CfgDlgProc:  ", hwnd, msg, wParam, lParam);
    #endif

    switch (msg)
    {
    case WM_DESTROY:
        EndConfig();
        return 0;

    case WM_INITDIALOG:
        { 
            // Initialize all config panel global variables:
            if (!InitConfig(hwnd))
            {
                MessageBox(hwnd, "InitConfig() failed!", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
                EndConfig();
                int id=LOWORD(wParam);
                EndDialog(hwnd,id);
                return false;
            }

            // set window caption
            SetWindowText( hwnd, WINDOWCAPTION );

            // Test for DirectX 8 + start it
            // note: if you don't call LoadLibrary here, and you're on a system
            //       where DX8 is missing, Direct3DCreate8() might crash; so call it.
            int d3d8_already_loaded = (GetModuleHandle("d3d8.dll") != NULL) ? 1 : 0;
            if (!d3d8_already_loaded)
                g_hmod_d3d8 = LoadLibrary("d3d8.dll");

            if ( (!d3d8_already_loaded && !g_hmod_d3d8) ||
                 !(g_lpDX = Direct3DCreate8(D3D_SDK_VERSION))
               )
            {
                MissingDirectX(hwnd);

                EndConfig();
                int id=LOWORD(wParam);
                EndDialog(hwnd,id);
                return false;
            }
                
            // enable the 'view website' button only if plugin author has #defined a URL (in defines.h):
            #ifndef PLUGIN_WEB_URL
                ShowWindow(GetDlgItem(hwnd, ID_WEB), SW_HIDE);
            #else
                if (strlen(PLUGIN_WEB_URL)==0)
                    ShowWindow(GetDlgItem(hwnd, ID_WEB), SW_HIDE);
            #endif

            // enable the 'view docs' button only if plugin author has #defined a filename (in defines.h):
            #ifndef DOCFILE
                ShowWindow(GetDlgItem(hwnd, ID_DOCS), SW_HIDE);
            #else
                if (strlen(DOCFILE)==0)
                    ShowWindow(GetDlgItem(hwnd, ID_DOCS), SW_HIDE);
            #endif

            // set contents of IDC_SZ_ABOUT
            SetWindowText( GetDlgItem(hwnd, IDC_SZ_ABOUT), LONGNAME" by "AUTHOR_NAME"\r\n"COPYRIGHT );

            // initialize tab control:
            {
                InitCommonControls();
    
                // Add Tabs:
                if (!AddButton(0, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_1) ||
                    !AddButton(1, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_2) ||
                    !AddButton(2, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_3) ||
                    !AddButton(3, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_4) ||
                    !AddButton(4, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_5) ||
                    !AddButton(5, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_6) ||
                    !AddButton(6, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_7) ||
                    !AddButton(7, GetDlgItem(hwnd,IDC_TABS), CONFIG_PANEL_BUTTON_8))
                {
                    MessageBox(hwnd, "Unable to load tabs!", "ERROR", MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
                    EndConfig();
                    int id=LOWORD(wParam);
                    EndDialog(hwnd,id);
                    return false;
                }

                // Simulate selection of the first tab. 
                OnTabChanged(0); 
            }

            g_ignore_clicks = 0;

            SetFocus(hwnd);
        }
        return 0;

    case WM_NOTIFY: 
        if (!g_ignore_clicks)
        {
            LPNMHDR pnmh = (LPNMHDR)lParam;
            switch(pnmh->code)
            {
            case TCN_SELCHANGE: 
                OnTabChanged(TabCtrl_GetCurSel(GetDlgItem(hwnd,IDC_TABS)));
                break;
            }
        }
        break;

    case WM_COMMAND:
        if (!g_ignore_clicks)
        {
            int id = LOWORD(wParam);
            switch(id)
            {
            case IDOK:
                
                // kill current tab window, so that its settings get read
                OnTabChanged(-1);

                // then save new config
                WriteConfig();

                EndDialog(hwnd,id);
                return 0;
            
            case IDCANCEL:
                EndDialog(hwnd,id);
                return 0;

            case ID_DOCS:
                {
                    char szPath[512], szFile[512];
                    lstrcpy(szPath, m_szConfigIniFile);
                    char *p = strrchr(szPath, '\\');
                    if (p != NULL)
                    {
                        *(p+1) = 0;
                        lstrcpy(szFile, szPath);
                        lstrcat(szFile, DOCFILE);
                        int ret = (int)ShellExecute(NULL, "open", szFile, NULL, szPath, SW_SHOWNORMAL);
                        if (ret <= 32)
                        {
                            char buf[1024];
                            switch(ret)
                            {
                            case SE_ERR_FNF:
                            case SE_ERR_PNF:
                                sprintf(buf, "the documentation file:\r\r\t%s\r\rcould not be found.", szFile);
                                break;
                            case SE_ERR_ACCESSDENIED:
                            case SE_ERR_SHARE:
                                sprintf(buf, "access to the documentation file:\r\r\t%s\r\rwas denied.", szFile);
                                break;
                            case SE_ERR_NOASSOC:
                                sprintf(buf, "the documentation file:\r\r\t%s\r\rcould not be accessed because there is no application\rassociated with documents of this type.", szFile);
                                break;
                            default:
                                sprintf(buf, "the documentation file:\r\r\t%s\r\rcould not be accessed (error code: %d)", szFile, ret);
                                break;
                            }
                            MessageBox(hwnd, buf, "error opening documentation", MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
                        }
                    }
                }
                break;

            case ID_WEB:
                {
                    int ret = (int)ShellExecute(NULL, "open", PLUGIN_WEB_URL, "", "", SW_SHOWNORMAL);
                    if (ret <= 32)
                    {
                        char buf[1024];
                        switch(ret)
                        {
                        case SE_ERR_FNF:
                        case SE_ERR_PNF:
                            sprintf(buf, "the URL\r\r\t%s\r\rcould not be opened.", PLUGIN_WEB_URL);
                            break;
                        case SE_ERR_ACCESSDENIED:
                        case SE_ERR_SHARE:
                            sprintf(buf, "access to the URL\r\r\t%s\r\rwas denied.", PLUGIN_WEB_URL);
                            break;
                        case SE_ERR_NOASSOC:
                            sprintf(buf, "the URL\r\r\t%s\r\rcould not be accessed because there is no application\rassociated with documents of this type.", PLUGIN_WEB_URL);
                            break;
                        default:
                            sprintf(buf, "the URL\r\r\t%s\r\rcould not be accessed (error code: %d)", PLUGIN_WEB_URL, ret);
                            break;
                        }
                        MessageBox(hwnd, buf, "error opening URL", MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
                    }
                }
                break;

            case ID_DEFAULTS:
                if (IDYES == MessageBox(hwnd, "Are you sure you want to restore the default settings?\r\nIf you click YES, the config panel will close and the defaults will be restored.", "Restore default settings?", MB_YESNO|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL))
                {
                    DeleteFile(m_szConfigIniFile);
                    Sleep(100);
                    EndDialog(hwnd,id);
                }
                break;

            default:
                return 0;
            }
        }
        break;        // case WM_COMMAND

    case WM_HELP:
        if (lParam)
        {
            HELPINFO *ph = (HELPINFO*)lParam;
            char title[256];
            char buf[2048];
            char ctrl_name[256];
            GetWindowText(GetDlgItem(hwnd, ph->iCtrlId), ctrl_name, sizeof(ctrl_name)-1);
            RemoveSingleAmpersands(ctrl_name);
            buf[0] = 0;
            switch(ph->iCtrlId)
            {
            case IDOK:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, "Click this button to save your changes and return to Winamp.");
                break;

            case IDCANCEL:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, "Click this button to cancel any changes and return to Winamp.");
                break;

            case ID_DEFAULTS:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, "Click this button to restore all config panel settings\rto their 'factory' defaults and then return to Winamp.");
                break;

            case ID_DOCS:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, "Click this button to view the documentation for this plugin.");
                break;

            case ID_WEB:
                sprintf(title, "Help on '%s' button", ctrl_name);
                sprintf(buf, "Click this button to view the website (homepage) for this plugin\rusing your default browser.");
                break;

            default:
                return 0;
            }

            if (buf[0])
                MessageBox(hwnd, buf, title, MB_OK|MB_SETFOREGROUND|MB_TOPMOST|MB_TASKMODAL);
        }
        break;  // case WM_HELP
    } 
    
    return 0;
}








